/*
Copyright (c) 2012 the VATSIM Connector project

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#ifndef __XPINN__DATAREFS_H__
#define __XPINN__DATAREFS_H__

#include "xpinn.h"
#include <XPLMDataAccess.h>
#include <vector>
#include <string>
#include <cassert>

//TODO doxygen comments

class XPInnDataRefImpl
{
public:
  XPInnDataRefImpl(char const* name) : m_ref(XPLMFindDataRef(name))
  {
    //TODO warn if m_ref is NULL
  }

  template <typename T>
  void implSet(T);
  template <typename T>
  T implGet() const;

private:
  XPLMDataRef m_ref;
};

class XPInnArrayDataRefImpl
{
public:
  XPInnArrayDataRefImpl(char const* name, size_t size) : m_ref(XPLMFindDataRef(name)), m_size(size)
  {
    //TODO warn if m_ref is NULL
  }

  template <typename T>
  void implSetAll(std::vector<T> const&);
  template <typename T>
  std::vector<T> implGetAll() const;
  template <typename T>
  void implSetAt(size_t index, T);
  template <typename T>
  T implGetAt(size_t index);

private:
  XPLMDataRef m_ref;
  size_t const m_size;
};

template<class DataRefTraits>
class XPInnDataRef : private XPInnDataRefImpl
{
public:
  XPInnDataRef() : XPInnDataRefImpl(DataRefTraits::name()) {}

  typedef DataRefTraits TraitsType;
  typedef typename DataRefTraits::type DataRefType;

  void set(typename DataRefType d) { XPInnDataRefImpl::implSet(d); }
  typename DataRefType get() const { return XPInnDataRefImpl::implGet<DataRefTraits::type>(); }
};

template<class DataRefTraits>
class XPInnArrayDataRef : private XPInnArrayDataRefImpl
{
public:
  XPInnArrayDataRef() : XPInnArrayDataRefImpl(DataRefTraits::name(), DataRefTraits::size) {}

  typedef DataRefTraits TraitsType;
  typedef typename DataRefTraits::type DataRefType;

  void setAll(std::vector<DataRefType> const& a) { XPInnArrayDataRefImpl::implSetAll(a); }
  std::vector<DataRefType> getAll() const { return XPInnArrayDataRefImpl::implGetAll<DataRefType>(); }

  void setAt(size_t index, DataRefType d) { XPInnArrayDataRefImpl::implSetAt(index, d); }
  DataRefType getAt(size_t index) const { return XPInnArrayDataRefImpl::implGetAt<DataRefType>(index); }
};

template<class DataRefTraits>
class XPInnStringDataRef
{
public:
  XPInnStringDataRef() : m_ref(XPLMFindDataRef(DataRefTraits::name()))
  {
    //TODO warn if m_ref is NULL
  }

  void set(std::string const& s) { setSubstr(0, s); }
  std::string get() const { return getSubstr(0, SIZE); }

  void setSubstr(size_t offset, std::string const& s)
  { assert((s.size() + 1) <= (SIZE - offset)); XPLMSetDatab(m_ref, s.c_str(), offset, s.size() + 1); }

  std::string getSubstr(size_t offset, size_t size) const
  { std::string s (size, 0); XPLMGetDatab(m_ref, &s[0], offset, size); size = s.find(char(0)); if (size != std::string::npos) s.resize(size); }

private:
  XPLMDataRef m_ref;
};

template <>
inline void XPInnDataRefImpl::implSet<int>(int d) { XPLMSetDatai(m_ref, d); }
template <>
inline void XPInnDataRefImpl::implSet<float>(float d) { XPLMSetDataf(m_ref, d); }
template <>
inline void XPInnDataRefImpl::implSet<double>(double d) { XPLMSetDatad(m_ref, d); }
template <>
inline int XPInnDataRefImpl::implGet<int>() const { return XPLMGetDatai(m_ref); }
template <>
inline float XPInnDataRefImpl::implGet<float>() const { return XPLMGetDataf(m_ref); }
template <>
inline double XPInnDataRefImpl::implGet<double>() const { return XPLMGetDatad(m_ref); }

template <>
inline void XPInnArrayDataRefImpl::implSetAll<int>(std::vector<int> const& v) { assert(v.size() <= m_size); XPLMSetDatavi(m_ref, const_cast<int*>(&v[0]), 0, v.size()); }
template <>
inline void XPInnArrayDataRefImpl::implSetAll<float>(std::vector<float> const& v) { assert(v.size() <= m_size); XPLMSetDatavf(m_ref, const_cast<float*>(&v[0]), 0, v.size()); }
template <>
inline std::vector<int> XPInnArrayDataRefImpl::implGetAll<int>() const { std::vector<int> v (m_size); XPLMGetDatavi(m_ref, &v[0], 0, m_size); }
template <>
inline std::vector<float> XPInnArrayDataRefImpl::implGetAll<float>() const { std::vector<float> v (m_size); XPLMGetDatavf(m_ref, &v[0], 0, m_size); }

template <>
inline void XPInnArrayDataRefImpl::implSetAt<int>(size_t i, int d) { assert(i <= m_size); XPLMSetDatavi(m_ref, &d, i, 1); }
template <>
inline void XPInnArrayDataRefImpl::implSetAt<float>(size_t i, float d) { assert(i <= m_size); XPLMSetDatavf(m_ref, &d, i, 1); }
template <>
inline int XPInnArrayDataRefImpl::implGetAt<int>(size_t i) { assert(i <= m_size); int d; XPLMGetDatavi(m_ref, &d, i, 1); return d; }
template <>
inline float XPInnArrayDataRefImpl::implGetAt<float>(size_t i) { assert(i <= m_size); float d; XPLMGetDatavf(m_ref, &d, i, 1); return d; }

#include "datarefs.inc"

template <unsigned int INDEX>
class XPInnPlaneDataRefsImpl;

#define DEFINE_PLANE_DATAREFS_IMPL(INDEX) \
  template <> \
  class XPInnPlaneDataRefsImpl<INDEX - 1> \
  { \
  public: \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_x> x; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_y> y; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_z> z; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_the> the; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_phi> phi; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_psi> psi; \
    XPInnArrayDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_gear_deploy> gear_deploy; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_flap_ratio> flap_ratio; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_flap_ratio2> flap_ratio2; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_spoiler_ratio> spoiler_ratio; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_speedbrake_ratio> speedbrake_ratio; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_slat_ratio> slat_ratio; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_wing_sweep> wing_sweep; \
    XPInnArrayDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_throttle> throttle; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_lat> lat; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_lon> lon; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_el> el; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_v_x> v_x; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_v_y> v_y; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_v_z> v_z; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_beacon_lights_on> beacon_lights_on; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_landing_lights_on> landing_lights_on; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_nav_lights_on> nav_lights_on; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_strobe_lights_on> strobe_lights_on; \
    XPInnDataRef<xplane::data::sim::multiplayer::position::plane##INDEX##_taxi_light_on> taxi_light_on; \
  };
DEFINE_PLANE_DATAREFS_IMPL(1)
DEFINE_PLANE_DATAREFS_IMPL(2)
DEFINE_PLANE_DATAREFS_IMPL(3)
DEFINE_PLANE_DATAREFS_IMPL(4)
DEFINE_PLANE_DATAREFS_IMPL(5)
DEFINE_PLANE_DATAREFS_IMPL(6)
DEFINE_PLANE_DATAREFS_IMPL(7)
DEFINE_PLANE_DATAREFS_IMPL(8)
DEFINE_PLANE_DATAREFS_IMPL(9)
DEFINE_PLANE_DATAREFS_IMPL(10)
DEFINE_PLANE_DATAREFS_IMPL(11)
DEFINE_PLANE_DATAREFS_IMPL(12)
DEFINE_PLANE_DATAREFS_IMPL(13)
DEFINE_PLANE_DATAREFS_IMPL(14)
DEFINE_PLANE_DATAREFS_IMPL(15)
DEFINE_PLANE_DATAREFS_IMPL(16)
DEFINE_PLANE_DATAREFS_IMPL(17)
DEFINE_PLANE_DATAREFS_IMPL(18)
DEFINE_PLANE_DATAREFS_IMPL(19)
#undef DEFINE_PLANE_DATAREFS_IMPL

#define CASE_IMPL(LEFT, INDEX, RIGHT) case INDEX: LEFT impl_##INDEX RIGHT; break;
#define SWITCH_IMPL(LEFT, INDEX_VAR, RIGHT) \
  switch (INDEX_VAR) \
  { \
    CASE_IMPL(LEFT, 0, RIGHT) \
    CASE_IMPL(LEFT, 1, RIGHT) \
    CASE_IMPL(LEFT, 2, RIGHT) \
    CASE_IMPL(LEFT, 3, RIGHT) \
    CASE_IMPL(LEFT, 4, RIGHT) \
    CASE_IMPL(LEFT, 5, RIGHT) \
    CASE_IMPL(LEFT, 6, RIGHT) \
    CASE_IMPL(LEFT, 7, RIGHT) \
    CASE_IMPL(LEFT, 8, RIGHT) \
    CASE_IMPL(LEFT, 9, RIGHT) \
    CASE_IMPL(LEFT, 10, RIGHT) \
    CASE_IMPL(LEFT, 11, RIGHT) \
    CASE_IMPL(LEFT, 12, RIGHT) \
    CASE_IMPL(LEFT, 13, RIGHT) \
    CASE_IMPL(LEFT, 14, RIGHT) \
    CASE_IMPL(LEFT, 15, RIGHT) \
    CASE_IMPL(LEFT, 16, RIGHT) \
    CASE_IMPL(LEFT, 17, RIGHT) \
    CASE_IMPL(LEFT, 18, RIGHT) \
    default: assert(false); \
  }

class XPInnPlanesDataRefs
{
public:
  double x(int index) { SWITCH_IMPL(return, index, .x.get()); }
  double y(int index) { SWITCH_IMPL(return, index, .y.get()); }
  double z(int index) { SWITCH_IMPL(return, index, .z.get()); }
  float the(int index) { SWITCH_IMPL(return, index, .the.get()); }
  float phi(int index) { SWITCH_IMPL(return, index, .phi.get()); }
  float psi(int index) { SWITCH_IMPL(return, index, .psi.get()); }
  std::vector<float> gear_deploy(int index) { SWITCH_IMPL(return, index, .gear_deploy.getAll()); }
  float flap_ratio(int index) { SWITCH_IMPL(return, index, .flap_ratio.get()); }
  float flap_ratio2(int index) { SWITCH_IMPL(return, index, .flap_ratio2.get()); }
  float spoiler_ratio(int index) { SWITCH_IMPL(return, index, .spoiler_ratio.get()); }
  float speedbrake_ratio(int index) { SWITCH_IMPL(return, index, .speedbrake_ratio.get()); }
  float slat_ratio(int index) { SWITCH_IMPL(return, index, .slat_ratio.get()); }
  float wing_sweep(int index) { SWITCH_IMPL(return, index, .wing_sweep.get()); }
  std::vector<float> throttle(int index) { SWITCH_IMPL(return, index, .throttle.getAll()); }
  double lat(int index) { SWITCH_IMPL(return, index, .lat.get()); }
  double lon(int index) { SWITCH_IMPL(return, index, .lon.get()); }
  double el(int index) { SWITCH_IMPL(return, index, .el.get()); }
  float v_x(int index) { SWITCH_IMPL(return, index, .v_x.get()); }
  float v_y(int index) { SWITCH_IMPL(return, index, .v_y.get()); }
  float v_z(int index) { SWITCH_IMPL(return, index, .v_z.get()); }
  int beacon_lights_on(int index) { SWITCH_IMPL(return, index, .beacon_lights_on.get()); }
  int landing_lights_on(int index) { SWITCH_IMPL(return, index, .landing_lights_on.get()); }
  int nav_lights_on(int index) { SWITCH_IMPL(return, index, .nav_lights_on.get()); }
  int strobe_lights_on(int index) { SWITCH_IMPL(return, index, .strobe_lights_on.get()); }
  int taxi_light_on(int index) { SWITCH_IMPL(return, index, .taxi_light_on.get()); }

  void x(int index, double val) { SWITCH_IMPL(, index, .x.set(val)); }
  void y(int index, double val) { SWITCH_IMPL(, index, .y.set(val)); }
  void z(int index, double val) { SWITCH_IMPL(, index, .z.set(val)); }
  void the(int index, float val) { SWITCH_IMPL(, index, .the.set(val)); }
  void phi(int index, float val) { SWITCH_IMPL(, index, .phi.set(val)); }
  void psi(int index, float val) { SWITCH_IMPL(, index, .psi.set(val)); }
  void gear_deploy(int index, std::vector<float> const& val) { SWITCH_IMPL(, index, .gear_deploy.setAll(val)); }
  void flap_ratio(int index, float val) { SWITCH_IMPL(, index, .flap_ratio.set(val)); }
  void flap_ratio2(int index, float val) { SWITCH_IMPL(, index, .flap_ratio2.set(val)); }
  void spoiler_ratio(int index, float val) { SWITCH_IMPL(, index, .spoiler_ratio.set(val)); }
  void speedbrake_ratio(int index, float val) { SWITCH_IMPL(, index, .speedbrake_ratio.set(val)); }
  void slat_ratio(int index, float val) { SWITCH_IMPL(, index, .slat_ratio.set(val)); }
  void wing_sweep(int index, float val) { SWITCH_IMPL(, index, .wing_sweep.set(val)); }
  void throttle(int index, std::vector<float> const& val) { SWITCH_IMPL(, index, .throttle.setAll(val)); }
  void lat(int index, double val) { SWITCH_IMPL(, index, .lat.set(val)); }
  void lon(int index, double val) { SWITCH_IMPL(, index, .lon.set(val)); }
  void el(int index, double val) { SWITCH_IMPL(, index, .el.set(val)); }
  void v_x(int index, float val) { SWITCH_IMPL(, index, .v_x.set(val)); }
  void v_y(int index, float val) { SWITCH_IMPL(, index, .v_y.set(val)); }
  void v_z(int index, float val) { SWITCH_IMPL(, index, .v_z.set(val)); }
  void beacon_lights_on(int index, int val) { SWITCH_IMPL(, index, .beacon_lights_on.set(val)); }
  void landing_lights_on(int index, int val) { SWITCH_IMPL(, index, .landing_lights_on.set(val)); }
  void nav_lights_on(int index, int val) { SWITCH_IMPL(, index, .nav_lights_on.set(val)); }
  void strobe_lights_on(int index, int val) { SWITCH_IMPL(, index, .strobe_lights_on.set(val)); }
  void taxi_light_on(int index, int val) { SWITCH_IMPL(, index, .taxi_light_on.set(val)); }

private:
  XPInnPlaneDataRefsImpl<0> impl_0;
  XPInnPlaneDataRefsImpl<1> impl_1;
  XPInnPlaneDataRefsImpl<2> impl_2;
  XPInnPlaneDataRefsImpl<3> impl_3;
  XPInnPlaneDataRefsImpl<4> impl_4;
  XPInnPlaneDataRefsImpl<5> impl_5;
  XPInnPlaneDataRefsImpl<6> impl_6;
  XPInnPlaneDataRefsImpl<7> impl_7;
  XPInnPlaneDataRefsImpl<8> impl_8;
  XPInnPlaneDataRefsImpl<9> impl_9;
  XPInnPlaneDataRefsImpl<10> impl_10;
  XPInnPlaneDataRefsImpl<11> impl_11;
  XPInnPlaneDataRefsImpl<12> impl_12;
  XPInnPlaneDataRefsImpl<13> impl_13;
  XPInnPlaneDataRefsImpl<14> impl_14;
  XPInnPlaneDataRefsImpl<15> impl_15;
  XPInnPlaneDataRefsImpl<16> impl_16;
  XPInnPlaneDataRefsImpl<17> impl_17;
  XPInnPlaneDataRefsImpl<18> impl_18;
};

#undef SWITCH_IMPL
#undef CASE_IMPL

#endif //__XPINN__DATAREFS_H__